﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using WMM.Tapioca.Formula.Filters;
using WMM.Tapioca.Linkbase;
using WMM.Tapioca.Instance;
using Saxon.Api;
using WMM.Tapioca.Utilities;

namespace WMM.Tapioca.Formula
{
    /// <summary>
    /// A value assertion is a statement about a value that is provided in or can be derived from an assertion input.
    /// </summary>
    public class ValueAssertion : VariableSetAssertion
    {   
        /// <summary>
        /// Constructor.
        /// </summary>
        public ValueAssertion(Resource resourceAssertion, Dictionary<string, string> namespaces)
            : base(resourceAssertion, namespaces)
        {
            Parameters = new List<Parameter>();
            FactVariables = new List<FactVariable>();
            GeneralVariables = new List<GeneralVariable>();
            VariableSetArcs = new List<VariableSet>();
            AssertionToMessageArcs = new List<Arc>();

            foreach (XAttribute attr in resourceAssertion.ResourceXElement.Attributes())
            {
                if (attr.Name.LocalName.Equals("aspectModel"))
                {
                    AspectModel = attr.Value;
                }

                if (attr.Name.LocalName.Equals("implicitFiltering"))
                {
                    ImplicitFiltering = Boolean.Parse(attr.Value);
                }

                if (attr.Name.LocalName.Equals("test"))
                {
                    Test = attr.Value;
                }

                if (attr.Name.LocalName.Equals("id"))
                {
                    Id = attr.Value;
                }
            }


            foreach (Arc arc in resourceAssertion.ExtendedLinkParent.Arcs)
            {
                if (arc.FromId.Equals(Label) &&
                    arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2008/element-label"))
                {
                    Linkbase.Resource resource = GetResource(arc.ToId, resourceAssertion.ExtendedLinkParent);
                    if (resource.LocalName.Equals("label"))
                    {
                        ElementLabel = resource.Content;
                    }
                }

                if (arc.FromId.Equals(Label) && 
                    arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2010/assertion-satisfied-message"))
                {
                    AssertionToMessageArcs.Add(arc);
                    Linkbase.Resource resource = GetResource(arc.ToId, resourceAssertion.ExtendedLinkParent);

                    if (resource.LocalName.Equals("message"))
                    {
                        MessageSatisfied = new Message(resource);
                    }
                }

                if (arc.FromId.Equals(Label) &&
                    arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2010/assertion-unsatisfied-message"))
                {
                    AssertionToMessageArcs.Add(arc);
                    Linkbase.Resource resource = GetResource(arc.ToId, resourceAssertion.ExtendedLinkParent);

                    if (resource.LocalName.Equals("message"))
                    {
                        MessageUnsatisfied = new Message(resource);
                    }
                }

                if (arc.FromId.Equals(Label) && arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2008/variable-set"))
                {
                    VariableSetArcs.Add(new VariableSet(arc));

                    List<Resource> resources = GetResources(arc.ToId, resourceAssertion.ExtendedLinkParent);

                    foreach (Resource resource in resources)
                    {

                        if (resource.LocalName.Equals("parameter"))
                        {
                            Parameters.Add(new Parameter(resource));
                        }

                        if (resource.LocalName.Equals("factVariable"))
                        {
                            FactVariables.Add(new FactVariable(resource));
                        }

                        if (resource.LocalName.Equals("generalVariable"))
                        {
                            GeneralVariables.Add(new GeneralVariable(resource));
                        }
                    }
                }

                if (arc.FromId.Equals(Label) && arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2008/variable-set-precondition"))
                {
                    Resource resource = GetResource(arc.ToId, resourceAssertion.ExtendedLinkParent);

                    if (resource.LocalName.Equals("precondition"))
                    {
                        Precondition = new Precondition(resource);
                    }
                }

                if (arc.FromId.Equals(Label) && arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2008/element-label"))
                {
                    LabelAssertion = GetResource(arc.ToId, resourceAssertion.ExtendedLinkParent);
                }                    
            }

            foreach (Arc arc in resourceAssertion.ExtendedLinkParent.Arcs)
            {
                if (arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2008/variable-filter"))
                {
                    foreach (FactVariable factVariable in FactVariables)
                    {
                        if (factVariable.Label.Equals(arc.FromId))
                        {
                            List<Resource> resources = GetResources(arc.ToId, arc.ExtendedLinkParent);

                            foreach (Resource resource in resources)
                            {
                                Filter filter = Filter.CreateFilter(arc, resource, this);
                                factVariable.Filters.Add(filter);
                                factVariable.VariableFilterArcs.Add(arc);

                                if (filter.Cover)
                                {
                                    AddCoveredAspect(factVariable, arc);
                                }
                            }
                        }
                    }
                }
            }

            foreach (Arc arc in resourceAssertion.ExtendedLinkParent.Arcs)
            {
                if (arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2008/variable-set-filter"))
                {
                    Resource filterResource = GetResource(arc.ToId, arc.ExtendedLinkParent);
                    GroupFilters.Add(Filter.CreateFilter(arc, filterResource, this));
                }
            }
        }


        /// <summary>
        /// Evaluating the assertion.
        /// </summary>
        public override bool EvaluateAssertion(InstanceDocument instance, FormulaProcessor formulaProcessor)
        {
            int k = 1;

            bool evaluateResult = false;

            this.dts = instance.Dts;
            namespaces = dts.DtsFormula.Namespaces;

            // clear
            variableItems.Clear();
            variableAsSequenceEntries.Clear();
            variableSequences.Clear();
            variableAsItem.Clear();
            variableNamesAsItem.Clear();
            parameterDictionary.Clear();
            dependencyGraph = new Graph();

            // Declare variable
            DeclareVariables(this, compiler);

            XPathSelector selector = compiler.Compile(test).Load();

            SetContextItem(processor, selector, instance.ThisXDocument.Root.GetXmlNode());

            BindParameters(selector, this, parameterDictionary);

            Dictionary<string, XdmAtomicValue> variableFallBack = new Dictionary<string, XdmAtomicValue>();

            // Evaluate variable set

            try
            {
                EvaluateFactVariables(this.variableItems, this.variableSequences, variableFallBack);
            }
            catch (Exception ex)
            {
                log4net.GlobalContext.Properties["assertionId"] = this.Id + " (evaluation " + k + ")";
                log4net.GlobalContext.Properties["satisfied"] = "Exception";
                FormulaProcessor.log.Error("Assertion:" + this.Id + " message:" + ex.Message);
                formulaProcessor.assertionsNotSatisfied++;
                k = k + 1;
                return false;
            }

            if (this.FactVariables.Count > 0 && this.variableItems.Count == 0 && this.variableSequences.Count == 0)
            {
                //log4net.GlobalContext.Properties["satisfied"] = "Not Evaluated";
                //FormulaProcessor.log.Error("Assertion:" + this.Id + " message: return 0 elements");
                //formulaProcessor.assertionsNotSatisfied++;
                return false;
            }            
            
            for (int i = 0; i < this.variableItems.Count; i++)
            {
                variableAsItem.Add(this.variableItems.ElementAt(i).Value);
                variableNamesAsItem.Add(this.variableItems.ElementAt(i).Key);
            }

            List<List<FactItem>> cartesianProductItems = ConvertToList(MyExtensions.CartesianProduct(variableAsItem).ToList());           

            foreach (List<FactItem> cartesianProductItem in cartesianProductItems)
            {
                Dictionary<string, FactItem> variableNotSequenceEntries = new Dictionary<string, FactItem>();

                variableAsSequenceEntries.Clear();

                if (this.ImplicitFiltering)
                {
                    if (this.ImplicitlyMatchItems(cartesianProductItem, variableNamesAsItem, variableSequences, this))
                    {
                        // Bind Variable

                        BindVariable(FormulaXBRL.processor, selector, cartesianProductItem, variableNamesAsItem, this.variableAsSequenceEntries, variableNotSequenceEntries, variableFallBack);

                        // Evaluate Precondition

                        Boolean precondition = EvaluatePrecondition(this.compiler, variableNotSequenceEntries, variableAsSequenceEntries, variableFallBack);                        

                        if (precondition)
                        {
                            log4net.GlobalContext.Properties["assertionId"] = this.Id + " (evaluation " + k + ")";

                            // Evaluate the XPath expression

                            try
                            {
                                evaluateResult = Boolean.Parse(selector.Evaluate().ToString());
                            }
                            catch (Exception ex)
                            {
                                log4net.GlobalContext.Properties["satisfied"] = "Exception";
                                FormulaProcessor.log.Error("Assertion:" + this.Id + " message:" + ex.Message);
                                formulaProcessor.assertionsNotSatisfied++;
                                return false;
                            }                            

                            BuildMessage(formulaProcessor, compiler, parameterDictionary, variableNotSequenceEntries, evaluateResult, variableFallBack);

                            k = k + 1;
                        }
                    }
                }
                else
                {
                    if (variableNamesAsItem.Count > 0)
                    {
                        // Bind Variable

                        BindVariable(FormulaXBRL.processor, selector, cartesianProductItem, variableNamesAsItem, this.variableAsSequenceEntries, variableNotSequenceEntries, variableFallBack);

                        // Evaluate Precondition

                        Boolean precondition = EvaluatePrecondition(this.compiler, variableNotSequenceEntries, variableAsSequenceEntries, variableFallBack);

                        if (precondition)
                        {
                            log4net.GlobalContext.Properties["assertionId"] = this.Id + " (evaluation " + k + ")";

                            // Evaluate the XPath expression

                            try
                            {
                                evaluateResult = Boolean.Parse(selector.Evaluate().ToString());
                            }
                            catch (Saxon.Api.DynamicError ex)
                            {
                                log4net.GlobalContext.Properties["satisfied"] = "Exception";
                                FormulaProcessor.log.Error("Assertion:" + this.Id + " message:" + ex.Message);
                                formulaProcessor.assertionsNotSatisfied++;
                                return false;
                            }          

                            BuildMessage(formulaProcessor, compiler, parameterDictionary, variableNotSequenceEntries, evaluateResult, variableFallBack);

                            k = k + 1;
                        }
                    }
                }                
            }

            if (this.variableSequences.Count > 0 && variableNamesAsItem.Count == 0)
            {
                Dictionary<string, FactItem> variableNotSequenceEntries = new Dictionary<string, FactItem>();

                List<FactItem> cartesianProductItem = new List<FactItem>();

                foreach (string sequenceName in this.variableSequences.Keys)
                {
                    foreach (List<FactItem> currentSequence in this.variableSequences[sequenceName])
                    {
                        variableAsSequenceEntries.Clear();

                        variableAsSequenceEntries.Add(sequenceName, currentSequence);

                        // Bind Variable

                        BindVariable(FormulaXBRL.processor, selector, cartesianProductItem, variableNamesAsItem, this.variableAsSequenceEntries, variableNotSequenceEntries, variableFallBack);

                        // Evaluate Precondition

                        Boolean precondition = EvaluatePrecondition(this.compiler, variableNotSequenceEntries, variableAsSequenceEntries, variableFallBack);

                        if (precondition)
                        {
                            log4net.GlobalContext.Properties["assertionId"] = this.Id + " (evaluation " + k + ")";

                            // Evaluate the XPath expression

                            try
                            {
                                evaluateResult = Boolean.Parse(selector.Evaluate().ToString());
                            }
                            catch (Saxon.Api.DynamicError ex)
                            {
                                log4net.GlobalContext.Properties["satisfied"] = "Exception";
                                FormulaProcessor.log.Error("Assertion:" + this.Id + " message:" + ex.Message);
                                formulaProcessor.assertionsNotSatisfied++;
                                return false;
                            }          

                            BuildMessage(formulaProcessor, compiler, parameterDictionary, variableNotSequenceEntries, evaluateResult, variableFallBack);

                            k = k + 1;
                        }

                    }
                }
            }

            if (this.FactVariables.Count == 0)
            {
                Dictionary<string, FactItem> variableNotSequenceEntries = new Dictionary<string, FactItem>();

                log4net.GlobalContext.Properties["assertionId"] = this.Id + " (evaluation " + k + ")";

                // Evaluate the XPath expression

                try
                {
                    evaluateResult = Boolean.Parse(selector.Evaluate().ToString());
                }
                catch (Saxon.Api.DynamicError ex)
                {
                    log4net.GlobalContext.Properties["satisfied"] = "Exception";
                    FormulaProcessor.log.Error("Assertion:" + this.Id + " message:" + ex.Message);
                    formulaProcessor.assertionsNotSatisfied++;
                    return false;
                }

                BuildMessage(formulaProcessor, compiler, parameterDictionary, variableNotSequenceEntries, evaluateResult, variableFallBack);

                k = k + 1;
            }


            return evaluateResult;
        }


    }
}
